import { isObject, isDate, isString, isNumber } from "./base"

// 输出后的时间对象
interface DateItem {
  $Y: number // 年
  $M: number // 月
  $D: number // 日
  $H: number // 时
  $m: number // 分
  $s: number // 秒
  $W: number // 周
  timestamp: number // 毫米数
  date: Date // 时间对象
  format?: string // 基于 format 输出的数据
}

function SET_FORMAT(date: DateItem): void {}

function DATE_TO_PARSE(date: Date): DateItem {
  date.setMilliseconds(0)

  const $Y = date.getFullYear()
  const $M = date.getMonth()
  const $D = date.getDate()
  const $H = date.getHours()
  const $m = date.getMinutes()
  const $s = date.getSeconds()
  const $W = date.getDay()
  const timestamp = date.getTime()

  const item = { $Y, $M, $D, $H, $m, $s, $W, timestamp, date }

  SET_FORMAT(item)

  return item
}

/* 1. 转换参数格式 */
type TimeType = Date | number | string
type ConvertedData = {
  time: TimeType
  format: string
}
function PARAMS_HANDLER(arg1?: TimeType | ConvertedData, arg2?: string) {
  const { time, format } = isObject(arg1) ? (arg1 as ConvertedData) : { time: arg1 as TimeType, format: arg2 }

  return {
    time: time || new Date(),
    format: format || "YYYY-MM-DD HH:mm:ss"
  }
}

/* 2. 解析 format */
// 从 format 中解析各个格式
const PATCH_ZERO = function (data: number, patch: boolean = false): string {
  if (patch) return data < 10 ? `0${data}` : `${data}`
  else return `${data}`
}
type DateType = {
  formats: Array<{
    fmt: string // format格式
    reg: string // 正则格式
    patch?: (date: number) => string // 补 0 方法
  }>
  write?: (date: Date, value: number) => void // 向 Date 中获取写入的方法
}
const FORMATS_UTILS: { [key: string]: DateType } = {
  $Y: {
    formats: [
      { fmt: "YYYY", reg: `[0-9]{4}`, patch: date => PATCH_ZERO(date) } // 0000-9999
    ],
    write: (date, v) => {
      date.setFullYear(v)
    }
  },
  $M: {
    formats: [
      { fmt: "MM", reg: `0[1-9]|1[0-2]`, patch: data => PATCH_ZERO(data + 1, true) }, // 01-12
      { fmt: "M", reg: `[1-9]|1[0-2]`, patch: data => PATCH_ZERO(data + 1) } // 1-12
    ],
    write: (date, v) => {
      date.setMonth(v - 1)
    }
  },
  $D: {
    formats: [
      { fmt: "DD", reg: `0[1-9]|[1-2][0-9]|3[0-1]`, patch: data => PATCH_ZERO(data, true) }, // 01-31
      { fmt: "D", reg: `[1-9]|[1-2][0-9]|3[0-1]`, patch: data => PATCH_ZERO(data) } // 1-31
    ],
    write: (date, v) => {
      date.setDate(v)
    }
  },
  $H: {
    formats: [
      { fmt: "HH", reg: `0[0-9]|1[0-9]|2[0-3]`, patch: data => PATCH_ZERO(data, true) }, // 00-23
      { fmt: "H", reg: `[0-9]|1[0-9]|2[0-3]`, patch: data => PATCH_ZERO(data) } // 0-23
    ],
    write: (date, v) => {
      date.setHours(v)
    }
  },
  $m: {
    formats: [
      { fmt: "mm", reg: `0[0-9]|[1-5][0-9]`, patch: data => PATCH_ZERO(data, true) }, // 00-59
      { fmt: "m", reg: `[0-9]|[1-5][0-9]`, patch: data => PATCH_ZERO(data) } // 0-59
    ],
    write: (date, v) => {
      date.setMinutes(v)
    }
  },
  $s: {
    formats: [
      { fmt: "ss", reg: `0[0-9]|[1-5][0-9]`, patch: data => PATCH_ZERO(data, true) }, // 00-59
      { fmt: "s", reg: `[0-9]|[1-5][0-9]`, patch: data => PATCH_ZERO(data) } // 0-59
    ],
    write: (date, v) => {
      date.setSeconds(v)
    }
  },
  $W: {
    formats: [
      { fmt: "WW", reg: `0[0-9]|[1-4][0-9]|5[0-2]`, patch: data => PATCH_ZERO(data, true) }, // 00-52
      { fmt: "W", reg: `[0-9]|[1-4][0-9]|5[0-2]`, patch: data => PATCH_ZERO(data) } // 0-52
    ]
  }
}
function PARSE_FORMAT(format: string) {
  let hadWeek = false // 存在周
  let hadYear = false // 存在年
  let hadOtherFmt = false // 非年 / 周格式

  const handler: any[] = []
  // 将所有 [] 去除掉
  const handlerFormat = format.replace(/\[[\w^\[\]]+\]/g, "")

  Object.keys(FORMATS_UTILS).forEach(key => {
    const { formats, write } = FORMATS_UTILS[key]

    const UTILS = formats.find(item => {
      return handlerFormat.includes(item.fmt)
    })

    if (UTILS) {
      handler.push({ key, write, ...UTILS })
      hadWeek = hadWeek ? hadWeek : key === "$W"
      hadYear = hadYear ? hadYear : key === "$Y"
      hadOtherFmt = hadOtherFmt ? hadOtherFmt : !["$Y", "$W"].includes(key)
    } else {
      handler.push({ key })
    }
  })

  return {
    handler,
    isWeek: hadWeek && hadYear,
    isError: hadOtherFmt ? hadWeek : false // 有其他非周年格式时不能有周格式
  }
}

/* 3. 入参类型判断 */
type TIME_TYPES = "string" | "timestamp" | "date"
function TIME_TYPE(time: TimeType): TIME_TYPES | false {
  if (isDate(time)) return "date" // 传入的是时间对象
  if (isString(time)) return "string" // 传入的是时间文本
  if (isNumber(time)) return "timestamp" // 传入的是时间戳
  return false // 传入的是无法解析的对象
}

/* 4. 根据解析后的 RegExp 从字符串类型中获取时间 */
const weeks = [7, 1, 2, 3, 4, 5, 6]
function GET_FIRST_WEEK(year, week) {
  const date = new Date(year, 0, 1, 0, 0, 0)
  const i = date.getDay()

  const diff = 24 * 60 * 60 * 1000 * (7 - weeks[i] + 1)
  const start = date.getTime() + diff
  const end = start + 24 * 60 * 60 * 1000 * 7 - 1

  const weekDiff = (week - 1) * 24 * 60 * 60 * 1000 * 7

  return [new Date(start + weekDiff), new Date(end + weekDiff)]
}
function PARSE_BY_REGEXP(time: string, format: string, handler: any[], target: string): string | false {
  let execFmt = format

  // 处理 execFmt
  let isFinish = false
  let execFmtArr: string[] = [execFmt] // 拆分后的结果
  let joinStr: string[] = [] // 用于拼接的字符数组
  while (!isFinish) {
    // 判断是否存在 [xxx]
    const sourceStr = execFmtArr.pop()
    const splitReg = new RegExp(/\[([\w^\[\]]+)\]/g)
    const res = splitReg.exec(sourceStr as string)
    if (res) {
      const [split, str] = res
      joinStr.push(str)
      const target = (sourceStr as string).split(split)
      const shift = target.shift()
      execFmtArr = [...execFmtArr, shift as string, target.join(split)]
    } else {
      isFinish = true
      execFmtArr.push(sourceStr || "")
    }
  }

  handler.forEach(item => {
    const { key, fmt, reg } = item
    const isTarget = key === target
    if (fmt) {
      // 找到第一个 fmt，替换为匹配正则
      execFmtArr.some((item, i) => {
        const isIncludes = item.includes(fmt)
        if (isIncludes) {
          execFmtArr[i] = execFmtArr[i].replace(fmt, isTarget ? `(${reg})` : `(?:${reg})`)
        }
        return isIncludes
      })

      // 重新拼接，去除 []
      execFmt = execFmtArr.reduce((total, str, i) => {
        return total + str + (joinStr[i] || "")
      }, "")
    }
  })

  const result = new RegExp(`^${execFmt}$`).exec(time)
  if (result) {
    const [, res] = result as any[]
    return res
  }
  return false
}
function READ_FROM_STR(time: string, format: string, handler: any[], isWeek = false) {
  if (isWeek) {
    const year = PARSE_BY_REGEXP(time, format, handler, "$Y")
    const week = PARSE_BY_REGEXP(time, format, handler, "$W")

    if (year && week) {
      return GET_FIRST_WEEK(year, week)
    } else {
      return false
    }
  } else {
    const date = new Date()
    date.setHours(0)
    date.setMinutes(0)
    date.setSeconds(0)
    let isError = false
    handler.forEach(item => {
      const { key, fmt, write } = item
      if (fmt) {
        const res = PARSE_BY_REGEXP(time, format, handler, key)
        isError = isError ? isError : !res
        if (res) write(date, res)
      }
    })
    return { isError, date }
  }
}

/* 结果 */
type ParseResult = DateItem | DateItem[] | false
export function useDate(arg1?: ConvertedData): ParseResult
export function useDate(arg1?: TimeType, arg2?: string): ParseResult
export function useDate(arg1?: ConvertedData | TimeType, arg2?: string): ParseResult {
  const { time, format } = PARAMS_HANDLER(arg1, arg2)

  const { isError, isWeek, handler } = PARSE_FORMAT(format)

  const type = TIME_TYPE(time)

  if (isError) return false
  if (!type) return false
  else if (isWeek) {
    if (type !== "string") return false
    else {
      const res = READ_FROM_STR(time as string, format, handler, true)
      if (!res) return false
      else {
        const [start, end] = res as Date[]
        return [DATE_TO_PARSE(start), DATE_TO_PARSE(end)]
      }
    }
  } else {
    let DATE: Date
    if (type === "string") {
      const { isError, date } = READ_FROM_STR(time as string, format, handler) as { isError: boolean; date: Date }
      if (isError) return false
      else DATE = date
    } else if (type === "timestamp") {
      DATE = new Date(time)
    } else if (type === "date") {
      DATE = time as Date
    } else return false

    return DATE_TO_PARSE(DATE)
  }
}
